﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Diagnostics.Contracts;
using System.Concurrency;

namespace ReactiveCode.Observables.Expressions
{
    using Util.Expressions;
    using E = Expression;

    static class ExpressionConvert
    {
        class FilteredObservableConverter: ObservableConverter
        {
            ParameterExpression _decorator;

            public ObservableFactory<IObservable<T>> Convert<T>(Expression expr)
            {
                _decorator = Expression.Parameter(typeof(IStateDecorator), "d");
                var body = Visit(expr);

                return Expression.Lambda<ObservableFactory<IObservable<T>>>(body, _decorator).Compile();
            }

            protected override Expression ListenMemberCore(Expression observableInstance, System.Reflection.MemberInfo member, Expression observableArguments)
            {
                var values = base.ListenMemberCore(observableInstance, member, observableArguments);

                if (values != null && _decorator != null)
                    values = E.Call(_decorator, "Apply", new[] {values.GetElementType()}, values);

                return values;
            }
        }
        public static ObservableFactory<IObservable<T>> ToObservable<T>(Expression expression)
        {
            Contract.Requires(expression != null);
            return new FilteredObservableConverter().Convert<T>(expression);
        }
        public static ObservableFactory<ISubject<T>> ToSubject<T>(Expression expression)
        {
            var observable = ToObservable<T>(expression);
            var setter = MakeSetter<T>(expression);
            var observer = Observer.Create(setter);

            return decorator => Subject.Create(observable(decorator), observer).Decorate(decorator);
        }
        static Action<T> MakeSetter<T>(Expression member)
        {
            var newValue = E.Parameter(typeof(T));
            var lambda = E.Lambda<Action<T>>
            (
                E.Assign(member, newValue),
                newValue
            );
            return lambda.Compile();
        }
    }
}
